#ifndef __AUTO_FIFO_HPP__
#define __AUTO_FIFO_HPP__

#include <boost/utility.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/thread.hpp>

#include <boost/variant.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/bind.hpp>

//#include <boost/thread/xtime.hpp>

#include <algorithm>
#include <iostream>
#include <cassert>
#include <deque>
#include <exception>
#include <string>

namespace vpm 
{
    namespace utils
    {
        using namespace boost; 

        template <class Fifo,class Handler>
            class AutoFifo  
            {
                public:
                    typedef boost::mutex::scoped_lock fifo_lock;
                    typedef typename Handler::element_type element_type; 

                    AutoFifo(Handler & handler):m_fifoHandler(handler)
                {
                    m_bRunFlag = true;        
                }

                    void start_fifo(int thrdNum = 2)
                    {
#ifdef _MULTI_FIFO_THREAD
                        for (int i=0; i<thrdNum; ++i)
                        {
                            m_workThreads.create_thread(boost::bind(&AutoFifo::run, this));
                        }
#else 
                        m_workThreads.create_thread(boost::bind(&AutoFifo::run, this));
#endif 
                    }

                    void stop_fifo()
                    {
                        m_bRunFlag = false; 
                        m_fifoNotEmpty.notify_one(); 
                    }

                    virtual ~AutoFifo()
                    {
                        m_bRunFlag = false; 
                        m_fifoNotEmpty.notify_all(); 
                        m_workThreads.join_all();
                    }

                    template <class E>
                        int post(const E& elem)
                        {
                            fifo_lock lock(m_writeMutex); 
                            m_writeQueue.push_back(elem); 
                            //m_fifoNotEmpty.notify_all(); 
                            m_fifoNotEmpty.notify_one(); 
                            return  0; 
                        }

                    void run()
                    {    
                        printf("=== Start up queue thread ===\n");
                        //while (!m_writeQueue.empty())
                        while(m_bRunFlag)
                        {

#ifdef  _MULTI_FIFO_THREAD  
                            while (m_readQueue.empty()) 
                            {
                                fifo_lock lock(m_writeMutex);  
                                if (m_writeQueue.empty())
                                {
                                    std::cout <<" start wait on empty read queue "<<std::endl; 
                                    m_fifoNotEmpty.wait(lock); 
                                    if (!m_bRunFlag)
                                    {
                                        break; 
                                    }
                                }
                                //two way to write the loop below . should test which one is better 
                                //first :

                                {
                                    fifo_lock lock(m_readMutex); 
                                    if (m_readQueue.empty() && !m_writeQueue.empty())
                                    {
                                        std::cout << "swap queue, read queue size:"<< m_readQueue.size() <<" write queue size :"<< m_writeQueue.size() <<"\n";
                                        m_readQueue.swap(m_writeQueue); 
                                        break;
                                    }

                                }

                                // second : 
                                //{
                                //    fifo_lock lock(m_readMutex); 
                                //    if (m_readQueue.empty())
                                //    {
                                //        std::cout << "swap queue, read queue size:"<< m_readQueue.size() <<" write queue size :"<< m_writeQueue.size() <<"\n";
                                //        if (m_writeQueue.empty())
                                //        {
                                //            continue; 
                                //        }
                                //        m_readQueue.swap(m_writeQueue); 
                                //    }
                                //    break;
                                //}
                            }

                            try
                            {
                                //this way will be better , for the iterator of deque is random , it's less inefficient 
                                element_type element = fetch_element(); 
                                boost::apply_visitor((Handler&)(m_fifoHandler),element); 
                            }
                            catch (const std::string & e)
                            {
                                (void) e; 
                                continue; 
                            }
#else 
                            //while (m_readQueue.empty()) //it must be empty here 
                            {
                                fifo_lock lock(m_writeMutex);  
                                if (m_writeQueue.empty())
                                {
                                    //cout <<" queue is empty"<<endl; 
                                    m_fifoNotEmpty.wait(lock); 
                                    if (!m_bRunFlag)
                                    {
                                        break; 
                                    }
                                }

                                // for read is single thread, don't need to lock 
                                if (m_readQueue.empty())
                                {
                                    m_readQueue.swap(m_writeQueue); 
                                }
                                std::cout << "swap read and write queue\n";
                            }

                            ////if (!m_writeQueue.empty())
                            //{
                            //    std::for_each(m_readQueue.begin(), m_readQueue.end(),
                            //    boost::apply_visitor((Handler&)(m_fifoHandler))) ; 
                            //    m_readQueue.clear();
                            //}

                            try 
                            {
                                while (!m_readQueue.empty())
                                {
                                    element_type element = fetch_element(); 
                                    boost::apply_visitor((Handler&)(m_fifoHandler),element); 
                                }
                            }
                            catch (const std::string & e)
                            {
                                (void)e; 
                                continue; 
                            }
#endif 
                            //   m_fifoNotEmpty.notify_all(); 

                        }

                    }

                private : 
                    element_type  fetch_element() 
                    {
#ifdef _MULTI_FIFO_THREAD
                        fifo_lock lock(m_readMutex); 
#endif 
                        if (m_readQueue.empty())
                        {
                            throw std::string("Queue is empty"); 
                        }
                        assert(!m_readQueue.empty());
                        element_type elem= m_readQueue.front(); 
                        m_readQueue.pop_front(); 
                        return elem; 
                    }

                    boost::thread_group m_workThreads;
                    Handler & m_fifoHandler; 
                    //DataVisitor m_dataVisitor; 
                    std::deque< element_type > m_writeQueue; 
                    std::deque< element_type > m_readQueue; 

                    boost::condition  m_fifoNotEmpty; 
                    boost::mutex m_writeMutex; 

#ifdef _MULTI_FIFO_THREAD
                    boost::mutex m_readMutex; 
#endif 

                    bool m_bRunFlag; 
            };

    }
}

#endif //__AUTO_FIFO_HPP__

